package com.qinge.blog;

import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.IntrospectedTable.TargetRuntime;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.TopLevelClass;

import java.util.*;

public class MapperAnnotationPlugin extends PluginAdapter {
    private final Collection<Annotations> annotations;

    public MapperAnnotationPlugin() {
        super();
        annotations = new LinkedHashSet<>(Annotations.values().length);
    }

    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }

    @Override
    public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        if (introspectedTable.getTargetRuntime() == TargetRuntime.MYBATIS3) {
            interfaze.addImportedType(
                    new FullyQualifiedJavaType("org.apache.ibatis.annotations.Mapper"));
            interfaze.addAnnotation("@Mapper");
        }
        return true;
    }

    /**
     * entity字段设置
     */
    @Override
    public boolean modelFieldGenerated(Field field, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
        if (field.getType().getShortNameWithoutTypeArguments().equals("Date")) {
            field.getAnnotations().add(Annotations.DATE_TIME_FORMAT.asAnnotation());
            field.getAnnotations().add(Annotations.JSON_FORMAT.asAnnotation());
            topLevelClass.addImportedType(Annotations.DATE_TIME_FORMAT.javaType);
            topLevelClass.addImportedType(Annotations.JSON_FORMAT.javaType);
        }

        if(field.getName().equals("createTime")){
            field.getAnnotations().add(Annotations.CREATE_TIME_ONE.asAnnotation());
            topLevelClass.addImportedType(Annotations.CREATE_TIME_ONE.javaType);
            topLevelClass.addImportedType(Annotations.CREATE_TIME_TWO.javaType);
        }

        // 有createTime就一定有createTime，所以不导入顶层依赖
        if(field.getName().equals("updateTime")){
            field.getAnnotations().add(Annotations.UPDATE_TIME.asAnnotation());
        }

        // 对boolean值进行填充
        if(field.getType().getShortNameWithoutTypeArguments().equals("Boolean")){
            field.getAnnotations().add(Annotations.BOOLEAN_ONE.asAnnotation());
            topLevelClass.addImportedType(Annotations.BOOLEAN_ONE.javaType);
            topLevelClass.addImportedType(Annotations.BOOLEAN_TWO.javaType);
        }

        return true;
    }

    private enum Annotations {

        DATA("data", "@Data", "lombok.Data"),
        BUILDER("builder", "@Builder", "lombok.Builder"),
        ALL_ARGS_CONSTRUCTOR("allArgsConstructor", "@AllArgsConstructor", "lombok.AllArgsConstructor"),
        NO_ARGS_CONSTRUCTOR("noArgsConstructor", "@NoArgsConstructor", "lombok.NoArgsConstructor"),
        ACCESSORS("accessors", "@Accessors", "lombok.experimental.Accessors"),
        TO_STRING("toString", "@ToString", "lombok.ToString"),
        DATE_TIME_FORMAT("dateTimeFormat", "@DateTimeFormat(pattern = \"yyyy-MM-dd HH:mm:ss\")", "org.springframework.format.annotation.DateTimeFormat"),
        JSON_FORMAT("jsonFormat", "@JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\")", "com.fasterxml.jackson.annotation.JsonFormat"),
        CREATE_TIME_ONE("createTimeOne","@TableField(fill = FieldFill.INSERT)","com.baomidou.mybatisplus.annotation.TableField"),
        CREATE_TIME_TWO("createTimeTwo","","com.baomidou.mybatisplus.annotation.FieldFill"),
        UPDATE_TIME("updateTime","@TableField(fill = FieldFill.INSERT_UPDATE)","com.baomidou.mybatisplus.annotation.TableField"),
        BOOLEAN_ONE("booleanOne","@TableField(fill = FieldFill.INSERT)","com.baomidou.mybatisplus.annotation.TableField"),
        BOOLEAN_TWO("booleanTwo","","com.baomidou.mybatisplus.annotation.FieldFill");

        private final String paramName;
        private final String name;
        private final FullyQualifiedJavaType javaType;
        private final List<String> options;

        Annotations(String paramName, String name, String className) {
            this.paramName = paramName;
            this.name = name;
            this.javaType = new FullyQualifiedJavaType(className);
            this.options = new ArrayList<String>();
        }

        private static Annotations getValueOf(String paramName) {
            for (Annotations annotation : Annotations.values()) {
                if (String.CASE_INSENSITIVE_ORDER.compare(paramName, annotation.paramName) == 0) {
                    return annotation;
                }
            }

            return null;
        }

        private static Collection<Annotations> getDependencies(Annotations annotation) {
            if (annotation == ALL_ARGS_CONSTRUCTOR) {
                return Collections.singleton(NO_ARGS_CONSTRUCTOR);
            } else {
                return Collections.emptyList();
            }
        }

        // A trivial quoting.
        // Because Lombok annotation options type is almost String or boolean.
        private static String quote(String value) {
            if (Boolean.TRUE.toString().equals(value) || Boolean.FALSE.toString().equals(value))
            // case of boolean, not passed as an array.
            {
                return value;
            }
            return value.replaceAll("[\\\\w]+", "\"$0\"");
        }

        private void appendOptions(String key, String value) {
            String keyPart = key.substring(key.indexOf(".") + 1);
            String valuePart = value.contains(",") ? String.format("{%s}", value) : value;
            this.options.add(String.format("%s=%s", keyPart, quote(valuePart)));
        }

        private String asAnnotation() {
            if (options.isEmpty()) {
                return name;
            }
            StringBuilder sb = new StringBuilder();
            sb.append(name);
            sb.append("(");
            boolean first = true;
            for (String option : options) {
                if (first) {
                    first = false;
                } else {
                    sb.append(", ");
                }
                sb.append(option);
            }
            sb.append(")");
            return sb.toString();
        }
    }
}
